const axios = require('axios');

class MazeNode {
    moves = []
    readyMoves = []
    beingMove = undefined
    beingMoveCount = 0

    constructor(moves = []) {
        this.moves = moves
    }

    get mayMoves() {
        return this.moves.filter(it => this.readyMoves.indexOf(it) === -1)
    }

    get rollBackMoves() {
        return Array(this.beingMoveCount).fill(reverseMove(this.beingMove))
    }

    nextMove() {
        if (this.beingMove && this.readyMoves.indexOf(this.beingMove) === -1) {
            this.readyMoves.push(this.beingMove)
        }
        if (this.mayMoves.length > 0) {
            this.beingMove = this.mayMoves[0]
            this.beingMoveCount = 1
            return this.beingMove
        }
        return null
    }
}

class MazeModel {

    nodes = []
    mazeView
    stage
    isWin
    debug

    addNode(node = new MazeNode()) {
        this.nodes.push(node)
    }

    get lastNode() {
        return this.nodes[this.nodes.length - 1]
    }

    rollBackLastNodeMoves() {
        const rollBackMoves = this.lastNode.rollBackMoves
        let nm = this.lastNode.nextMove()
        if (!nm) {
            this.nodes.pop()
            return [...rollBackMoves, ...this.rollBackLastNodeMoves()]
        }
        return [...rollBackMoves, nm]
    }

    restNodes() {
        this.nodes = []
    }

}

async function start(options) {
    console.log("auto-maze by mitu2")
    const { targetStage, debug, cookie } = options

    const client = axios.create({
        baseURL: 'https://maze.hancel.org',
        headers: {
            Cookie: cookie,
        },
    });

    const onAction = async (action) => await client
        .post(`/api/maze/${ action }`, {})
        .then(({ data }) => {
            if (data.code === -1) {
                throw 'Not login or request fail'
            }
            return data
        })

    const Actions = {
        last: () => onAction('last'),
        next: () => onAction('next'),
        right: () => onAction('move/right'),
        left: () => onAction('move/left'),
        up: () => onAction('move/up'),
        down: () => onAction('move/down'),
    }

    const model = new MazeModel()
    model.debug = debug
    const onMazeAction = async (action) => {
        if (debug) {
            console.log(`action ${ action }: ${ Actions[action] }`)
        }
        const { mazeView: _mazeView, stage: _stage, isWin: _isWin } = await Actions[action]()
        model.mazeView = _mazeView
        model.stage = _stage
        model.isWin = _isWin
    }
    await onMazeAction('last')

    while (model.stage < targetStage) {
        let result = analyseMazeView(model);
        for (let _move of result) {
            await onMazeAction(_move)
        }
    }
}

function readCell(cell) {
    if (typeof cell === 'string') {
        return {
            isWall: cell === 'wall',
            isBegin: cell === 'begin',
            count: (function () {
                // noinspection JSUnresolvedReference
                return this.isWall || !cell.startsWith('count-') ? -1 : parseInt(cell.replace('count-', ''))
            }()),
            goalDirection: false,
            isGoal: cell === 'goal'
        }
    }
    return {
        isWall: cell.indexOf('wall') !== -1,
        isBegin: cell.indexOf('begin') !== -1,
        count: (function () {
            // noinspection JSUnresolvedReference
            if (this.isWall) {
                return -1
            }
            const countProp = cell.findLast(it => it.startsWith('count-'))
            return !countProp ? -1 : parseInt(countProp.replace('count-', ''))
        }()),
        goalDirection: cell.indexOf('goal-direction') !== -1,
        isGoal: cell.indexOf('goal') !== -1
    }
}

const reverseMove = key => {
    switch (key) {
        case 'up':
            return 'down'
        case 'down':
            return 'up'
        case 'left':
            return 'right'
        case 'right':
            return 'left'
        default:
            return null
    }
}

function analyseMazeView(model) {
    const { mazeView, isWin, stage } = model
    if (isWin) {
        model.restNodes()
        console.log(`stage ${ stage } win!`)
        return ['next']
    }
    const map = []
    for (let row = 0; row < mazeView.length; row++) {
        let rowValue = mazeView[row]
        for (let col = 0; col < rowValue.length; col++) {
            map[row] = map[row] || []
            map[row][col] = readCell(rowValue[col])
        }
    }
    // noinspection JSUnusedLocalSymbols
    const [
        [leftUp, up, rightUp],
        [left, center, right],
        [leftDown, down, rightDown]
    ] = map

    const option = {
        right, down, up, left
    }

    if (Object.values(option).filter(it => !it.isWall).length === 0) {
        throw 'WDNMD, Game Over?'
    }

    const mays = []

    for (let [key, loc] of Object.entries(option)) {
        // 如果有终点直接去终点
        if (loc.isGoal) {
            return [key]
        }
        // 缓存不是墙的
        if (!loc.isWall) {
            mays.push({
                key, loc
            })
        }
    }

    const _mays = mays.map(it => it.key);
    if (!model.lastNode) {
        const node = new MazeNode(_mays)
        const next = node.nextMove()
        model.addNode(node)
        return [next];
    }
    // noinspection FallThroughInSwitchStatementJS
    switch (mays.length) {
        case 1:
            if (model.debug) {
                console.log('roll-back')
            }
            return model.rollBackLastNodeMoves()
        case 2:
            if (_mays.indexOf(model.lastNode.beingMove) !== -1) {
                model.lastNode.beingMoveCount += 1
                return [model.lastNode.beingMove]
            }
        case 3:
        case 4:
            const node = new MazeNode(_mays)
            node.readyMoves.push(reverseMove(model.lastNode.beingMove))
            const next = node.nextMove()
            model.addNode(node)
            return [next];
        default:
            throw 'WDNMD, where is the'
    }

}

exports.start = start